{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## 定数\n",
    "\n",
    "* Rubyは定数の値を変更できるという意味では変数とも言える\n",
    "    * Warningは出る\n",
    "    * 破壊的メソッドをつかって値を変更するときはWarningは出ない\n",
    "* 命名規則\n",
    "    * 先頭\n",
    "        * 英数大文字\n",
    "    * 構成文字\n",
    "        * 英数字又はアンダースコア\n",
    "* スコープ\n",
    "    * 定数が定義されたクラス・モジュール\n",
    "    * そのクラス・モジュール内で定義されたクラス・モジュール\n",
    "    * そのクラス・モジュールをインクルードしているモジュール\n",
    "    * クラス名やモジュール名で修飾すれば外部からアクセス可能\n",
    "* 初期化されていない時の参照\n",
    "    * 例外発生(NameError)\n",
    "* <font color=\"red\">メソッドの中で定義することはできない</font>\n",
    "    * メソッドは複数回の実行が前提なので定数の初期化、更新が許されていない"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"hoge\"\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "(pry):6: warning: already initialized constant AAA\n",
      "(pry):2: warning: previous definition of AAA was here\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"fuga\"\n",
      "\"fugahaga\"\n",
      "uninitialized constant BBB\n"
     ]
    }
   ],
   "source": [
    "### 変数名の先頭を大文字にすると定数になる\n",
    "AAA = \"hoge\"\n",
    "p AAA\n",
    "\n",
    "### 警告は出るが再代入はできる\n",
    "AAA = \"fuga\"\n",
    "p AAA\n",
    "\n",
    "### 破壊的メソッドによる変更は警告は出ない\n",
    "AAA.concat(\"haga\")\n",
    "p AAA\n",
    "\n",
    "### 初期化されていない時の参照は例外が発生\n",
    "begin\n",
    "  p BBB\n",
    "rescue NameError => ex\n",
    "  puts ex\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SyntaxError: (eval):5: dynamic constant assignment\n",
      "  BBB = 1\n",
      "       ^\n"
     ]
    }
   ],
   "source": [
    "### メソッドの中で定数を定義しようとするとエラーになる\n",
    "\n",
    "def method1\n",
    "  BBB = 1\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### ネストした定数\n",
    "\n",
    "* モジュールオブジェクトやクラスオブジェクトが格納された定数に対しては「::」という演算子を指定してネストを指定できる"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "module ModuleA\n",
    "end\n",
    "\n",
    "### ModuleAの中に定数Aを宣言\n",
    "ModuleA::A = 1\n",
    "\n",
    "puts ModuleA::A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "(pry):104: warning: already initialized constant ModuleM::A\n",
      "(pry):29: warning: previous definition of A was here\n",
      "(pry):106: warning: already initialized constant ModuleM::ClassB::A\n",
      "(pry):31: warning: previous definition of A was here\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ClassCには定数Aは無いので、ClassCの外側のModuleMの定数Aを参照する\n",
      "ClassC : A = 1\n",
      "ModuleMの定数AとClassBの定数Aは異なる\n",
      "1\n",
      "2\n",
      "ModuleMを再オープンして中に入って相対位置で定数を参照\n",
      "1\n",
      "2\n",
      "ModuleMを再オープンして中に入ってルートからの絶対位置で定数を参照\n",
      "1\n",
      "2\n"
     ]
    }
   ],
   "source": [
    "### モジュール式でネストした定数\n",
    "\n",
    "module ModuleM\n",
    "  A = 1\n",
    "  class ClassB\n",
    "    A = 2\n",
    "  end\n",
    "  class ClassC\n",
    "    puts \"ClassCには定数Aは無いので、ClassCの外側のModuleMの定数Aを参照する\"\n",
    "    puts \"ClassC : A = #{A}\"\n",
    "  end\n",
    "end\n",
    "\n",
    "puts \"ModuleMの定数AとClassBの定数Aは異なる\"\n",
    "puts ModuleM::A\n",
    "puts ModuleM::ClassB::A\n",
    "\n",
    "puts \"ModuleMを再オープンして中に入って相対位置で定数を参照\"\n",
    "module ModuleM\n",
    "  puts A\n",
    "  puts ClassB::A\n",
    "end\n",
    "\n",
    "puts \"ModuleMを再オープンして中に入ってルートからの絶対位置で定数を参照\"\n",
    "module ModuleM\n",
    "  puts ::ModuleM::A\n",
    "  puts ::ModuleM::ClassB::A\n",
    "end\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[ModuleM::ClassC, ModuleM]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "### ネストした現在のPATHの確認\n",
    "module ModuleM\n",
    "  class ClassC\n",
    "    Module.nesting\n",
    "  end\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### constantsメソッド\n",
    "\n",
    "* Module#constants\n",
    "    * Moduleクラスのインスタンスメソッド\n",
    "    * 現在のスコープにある全ての定数を返す\n",
    "    * 外部から参照可能な定数を調べる\n",
    "    * レシーバ(またはそのスーパークラスやインクルードしているモジュール)に定義されている定数の名前を配列で返す\n",
    "        * クラスは定数なのでクラス名も返す\n",
    "* Module.constants\n",
    "    * Moduleのクラスメソッド\n",
    "    * 現在のプログラムのトップレベルにある全ての定数を返す"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[:NNN, :ClassD]\n",
      "[:DDD]\n"
     ]
    }
   ],
   "source": [
    "module ModuleN\n",
    "  NNN = 1\n",
    "  class ClassD\n",
    "    DDD = 2\n",
    "  end\n",
    "end\n",
    "\n",
    "puts ModuleN.constants\n",
    "puts ModuleN::ClassD.constants"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[:Object, :Module, :Class, :BasicObject, :Kernel, :NilClass, :NIL, :Data, :TrueClass, :TRUE, :FalseClass, :FALSE, :Encoding, :Comparable, :Enumerable, :String, :Symbol, :Exception, :SystemExit, :SignalException, :Interrupt, :StandardError, :TypeError, :ArgumentError, :IndexError, :KeyError, :RangeError, :ScriptError, :SyntaxError, :LoadError, :NotImplementedError, :NameError, :NoMethodError, :RuntimeError, :SecurityError, :NoMemoryError, :EncodingError, :SystemCallError, :Errno, :UncaughtThrowError, :ZeroDivisionError, :FloatDomainError, :Numeric, :Integer, :Fixnum, :Float, :Bignum, :Array, :Hash, :ENV, :Struct, :RegexpError, :Regexp, :MatchData, :Marshal, :Range, :IOError, :EOFError, :IO, :STDIN, :STDOUT, :STDERR, :ARGF, :FileTest, :File, :Dir, :Time, :Random, :Signal, :Proc, :LocalJumpError, :SystemStackError, :Method, :UnboundMethod, :Binding, :Math, :GC, :ObjectSpace, :Enumerator, :StopIteration, :RubyVM, :Thread, :TOPLEVEL_BINDING, :ThreadGroup, :ThreadError, :ClosedQueueError, :Mutex, :Queue, :SizedQueue, :ConditionVariable, :Process, :Fiber, :FiberError, :Rational, :Complex, :RUBY_VERSION, :RUBY_RELEASE_DATE, :RUBY_PLATFORM, :RUBY_PATCHLEVEL, :RUBY_REVISION, :RUBY_DESCRIPTION, :RUBY_COPYRIGHT, :RUBY_ENGINE, :RUBY_ENGINE_VERSION, :TracePoint, :ARGV, :Gem, :DidYouMean, :RbConfig, :CROSS_COMPILING, :StringIO, :MonitorMixin, :Monitor, :Pathname, :Bundler, :Etc, :FileUtils, :URI, :Digest, :Set, :SortedSet, :TSort, :Forwardable, :SingleForwardable, :MultiJson, :IRuby, :Logger, :MimeMagic, :OpenSSL, :SecureRandom, :Delegator, :SimpleDelegator, :Tempfile, :In, :Out, :ZMQ, :BasicSocket, :Socket, :SocketError, :IPSocket, :TCPSocket, :TCPServer, :UDPSocket, :UNIXSocket, :UNIXServer, :Addrinfo, :ZL, :JSON, :OpenStruct, :Singleton, :PrettyPrint, :PP, :Pry, :MethodSource, :Shellwords, :CodeRay, :Slop, :Readline, :AAA, :ModuleA, :ModuleM, :ModuleN, :ModuleO, :RUBYGEMS_ACTIVATION_MONITOR]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Module.constants"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### const_missingメソッド\n",
    "\n",
    "* const_missing\n",
    "    * スーパークラスにも定数が見つからない場合に呼び出されるメソッド\n",
    "    * このメソッドをサブクラスで上書きすれば定数が見つからない場合の動作を制御できる"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"Const not found\""
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "### 定数が見つからないときメッセージを表示する\n",
    "module ModuleO\n",
    "  def self.const_missing(dummy)\n",
    "    \"Const not found\"\n",
    "  end\n",
    "end\n",
    "\n",
    "ModuleO::HOGE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "## ネームスペース\n",
    "\n",
    "* 定数をまとめるだけのモジュール\n",
    "    * 例えばRakeのTaskは他のクラスと名前が衝突することがよくあったので、Taskクラスの完全な名前は Rake::Task になった"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "class Text   ### <- いかにもどこかに使われていて衝突しそうなクラス名\n",
    "  \n",
    "↓\n",
    "  \n",
    "module Bookworm\n",
    "  class Text    ### 例えばBookworm::Textとすると、かぶる心配が少ない\n",
    "    ..."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Ruby 2.3.1",
   "language": "ruby",
   "name": "ruby"
  },
  "language_info": {
   "file_extension": ".rb",
   "mimetype": "application/x-ruby",
   "name": "ruby",
   "version": "2.3.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
